<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace Hyperfx\Framework\Middleware;

use Google\Protobuf\Internal\Message as ProtobufMessage;
use Hyperf\Contract\ContainerInterface;
use Hyperf\Di\MethodDefinitionCollector;
use Hyperf\Di\ReflectionManager;
use Hyperf\Grpc\Parser;
use Hyperf\Server\Exception\ServerException;
use Hyperf\Utils\Network;
use Hyperfx\Framework\Logger\Logx;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Hyperf\HttpServer\Router\Dispatched;
use Hyperf\Context\Context;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class CoreMiddleware implements MiddlewareInterface
{
    public function __construct(protected ContainerInterface $container)
    {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // 日志为DEBUG才执行
        if (Logx::isDebugLevel()) {

            // full url
            $fullUrl = sprintf('%s://%s:%s%s',
                $request->getUri()->getScheme(),
                $request->getUri()->getHost(),
                $request->getUri()->getPort(),
                $request->getUri()->getPath()
            );

            Logx::get()->debug('Request', [
                'action' => $fullUrl,
                'method' => $request->getMethod(),
                'param' => [
                    'query' => $request->getQueryParams(),
                    'body' => $this->getBody($request),
                ],
                'referer' => $request->getHeader('referer') ?: '',
                'user_agent' => $request->getHeaderLine('user-agent') ?: '',
                'client_ip' => Network::ip(),
            ]);

        }
        return $handler->handle($request);
    }

    private function getBody(ServerRequestInterface $request) {
        if (0 == strcmp($request->getHeaderLine('content-type'), 'application/grpc+proto')) {
            // grpc
            /** @var Dispatched $dispatched */
            $dispatched = $request->getAttribute(Dispatched::class);

            if (! $dispatched instanceof Dispatched) {
                throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class));
            }
            [$controller, $action] = $this->prepareHandler($dispatched->handler->callback);
            $parameters = $this->parseMethodParameters($controller, $action, $dispatched->params);
            if (isset($parameters[0])) {
                return $parameters[0]->serializeToJsonString();
            }
        }
        // http
        return $request->getParsedBody();
    }

    /**
     * @return RequestInterface
     */
    protected function request()
    {
        return Context::get(ServerRequestInterface::class);
    }

    protected function parseMethodParameters(string $controller, string $action, array $arguments): array
    {
        $injections = [];
        $definitions = MethodDefinitionCollector::getOrParse($controller, $action);

        foreach ($definitions ?? [] as $definition) {
            if (! is_array($definition)) {
                throw new \RuntimeException('Invalid method definition.');
            }
            if (! isset($definition['type']) || ! isset($definition['name'])) {
                $injections[] = null;
                continue;
            }
            $injections[] = value(function () use ($definition) {
                switch ($definition['type']) {
                    case 'object':
                        $ref = $definition['ref'];
                        $class = ReflectionManager::reflectClass($ref);
                        $parentClass = $class->getParentClass();
                        if ($parentClass && $parentClass->getName() === ProtobufMessage::class) {
                            $request = $this->request();
                            $stream = $request->getBody();
                            return Parser::deserializeMessage([$class->getName(), null], (string) $stream);
                        }

                        if (! $this->container->has($definition['ref']) && ! $definition['allowsNull']) {
                            throw new \RuntimeException(sprintf('Argument %s invalid, object %s not found.', $definition['name'], $definition['ref']));
                        }

                        return $this->container->get($definition['ref']);
                    default:
                        throw new \RuntimeException('Invalid method definition detected.');
                }
            });
        }

        return $injections;
    }

    protected function prepareHandler($handler): array
    {
        if (is_string($handler)) {
            if (strpos($handler, '@') !== false) {
                return explode('@', $handler);
            }
            return explode('::', $handler);
        }
        if (is_array($handler) && isset($handler[0], $handler[1])) {
            return $handler;
        }
        throw new \RuntimeException('Handler not exist.');
    }
}
